// <copyright file="SSISConsoleDeploy.cs" company="Bill Fellows">
// Copyright (c) This makes Source Analysis happy. All rights reserved.
// </copyright>
namespace SSISDeploy.Components
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.IO;
    using System.Xml;

    /// <summary>
    /// The current methods of deploying SSIS packages to SQL server are broken in my opinion.
    /// My current engagement uses an automated build process with a major wart being that the
    /// SSIS deploy process uses the graphical manifest deploy utility.  Another pain is that
    /// one cannot specify folders for use with it.  A third pain is that old packages are not
    /// removed.
    /// 
    /// This goal of this project is to create a stronger deploy utility for SSIS packages.
    /// Currently it will wrap the existing dtutil program and make use of its capabilities.
    /// With some luck, I may find a object model in sql server that lets me do this more gracefully.
    /// I also look forward to using the existing manifest as a means of discovering the
    /// approved packages for release.
    /// 
    /// This may also be an opportunity to engage powershell.
    /// </summary>
    public class SSISConsoleDeploy
    {
        /// <summary>
        /// The fully qualified path to the dtutil executable
        /// </summary>
        private DtUtilWrapper dtUtil;

        /// <summary>
        /// The default constructor.  For now, it has a hardcoded path but it might be
        /// better served by pulling information from the registry or allowing it as an
        /// application option/command line argument.  Need to look at 64 bit to see
        /// how it is set up in registries.
        /// </summary>
        public SSISConsoleDeploy()
        {
            this.dtUtil = new DtUtilWrapper();
        }

        /// <summary>
        /// Create a folder on SQL server.
        /// TODO:  Globally, figure out what will be the most appropriate way of handling
        /// and reporting errors.
        /// </summary>
        /// <param name="server">A SQL Server name</param>
        /// <param name="parentFolderPath">The path to the parent folder</param>
        /// <param name="newFolderName">The name of the folder to be created</param>
        /// <returns>true/false based on the successful creation of a folder</returns>
        public bool CreateFolder(string server, string parentFolderPath, string newFolderName)
        {
            return this.dtUtil.CreateFolder(server, parentFolderPath, newFolderName);
        }

        /// <summary>
        /// Delete a folder on SQL Server
        /// </summary>
        /// <param name="server">A SQL Server server</param>
        /// <param name="parentFolderPath">The path to the parent folder</param>
        /// <param name="folderName">The name of the folder to be removed</param>
        /// <returns>true if a folder was deleted</returns>
        public bool DeleteFolder(string server, string parentFolderPath, string folderName)
        {
            return this.dtUtil.DeleteFolder(server, parentFolderPath, folderName);
        }

        /// <summary>
        /// Delete everything below the path provided
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="parentFolderPath">A fully qualified path where the deletion should be rooted</param>
        /// <returns>true if everything was removed as expected</returns>
        public bool DeleteFolderRecursive(string server, string parentFolderPath)
        {
            return this.dtUtil.DeleteFolderRecursive(server, parentFolderPath);
        }

        /// <summary>
        /// Remove a particular package from SQL Server
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="parentFolderPath">The enclosing folder for a package</param>
        /// <param name="packageName">The name of the package to remove</param>
        /// <returns>true if the package was deleted successfully</returns>
        public bool DeletePackage(string server, string parentFolderPath, string packageName)
        {
            return this.dtUtil.DeletePackage(server, parentFolderPath, packageName);
        }

        /// <summary>
        /// Upload a package from the file system into SQL Server
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="pathToDtsx">The path on the file system to an SSIS package</param>
        /// <param name="packagePathAndName">The path within SQL server and the desired name for the package</param>
        /// <returns>true if the upload was successful</returns>
        public bool CopyPackage(string server, string pathToDtsx, string packagePathAndName)
        {
            return this.dtUtil.CopyPackage(server, pathToDtsx, packagePathAndName);
        }

        /// <summary>
        /// Test whether a package exists
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="packagePathAndName">The path within SQL server with the package name</param>
        /// <returns>true if the package exists</returns>
        public bool PackageExists(string server, string packagePathAndName)
        {
            return this.dtUtil.PackageExists(server, packagePathAndName);
        }

        /// <summary>
        /// Test whether a folder exists
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="folderPath">The path within SQL server</param>
        /// <returns>true if the package exists</returns>
        public bool FolderExists(string server, string folderPath)
        {
            return this.dtUtil.FolderExists(server, folderPath);
        }

        /// <summary>
        /// Load everything in the manifest into the packagePath, creating hierarchy structures as needed and
        /// removing anything not in the manifest
        /// </summary>
        /// <param name="server">The SQL Server servername</param>
        /// <param name="packagePath">The destination within SQL Server</param>
        /// <param name="manifestPath">A location on disk where a manifest may be found</param>
        /// <param name="simulateOnly">Indicator of whether it should only script out the changes to be taken</param>
        /// <returns>true if it was able to deploy the files in the manifest</returns>
        public bool DeployManifest(string server, string packagePath, string manifestPath, bool simulateOnly)
        {
            System.Collections.Generic.List<string> packages = null;
            string baseFolderOnDisk = string.Empty;
            System.IO.DirectoryInfo dir = null;
            bool results = false;

            this.dtUtil.SimulateOnly = simulateOnly;

            try
            {
                // Find the path the manifest is sitting in
                dir = System.IO.Directory.GetParent(manifestPath);
                baseFolderOnDisk = dir.ToString();
            }
            catch (FileNotFoundException exception)
            {
                Console.WriteLine(string.Format("Manifest not found or insufficient privileges.  Manifest = {0}", manifestPath));
                return false;
            }

            // Get a listing of the packaages to be uploaded to SQL Server
            packages = this.GetPackageListFromManifest(manifestPath);

            if (packages == null)
            {
                Console.WriteLine(string.Format("Manifest not found or insufficient privileges.  Manifest = {0}", manifestPath));
                return false;
            }

            // at this point we know all the files and where they should exist.
            // Assume they've also passed in the sql server folder, default of \
            if (!packagePath.StartsWith("\\"))
            {
                // This would be a warning or informational message
                Console.WriteLine(string.Format("Paths for SQL Server must begin with a leading slash.  Path has been adjusted to \\{0}", packagePath));
                packagePath = string.Format("\\{0}", packagePath);
            }

            // 1)  If the folder/path exists, purge it and any sub-entries
            // 2)  Create the folder/path if it does not exist
            // 3)  Iterate through the items from the manifest
            //     Verify they exist on disk and attempt to store to sql server
            // 4)  Print out a listing of everything deleted
            //     Print out as listing of everything created (grab versions for packages)
            if (this.FolderExists(server, packagePath))
            {
                this.dtUtil.DeleteFolderRecursive(server, packagePath);
            }

            this.CreateFoldersRecursive(server, packagePath);

            // TODO:  validate packages are intact on the disk

            // Load packages into SQL Server
            foreach (string package in packages)
            {
                string ssisName = string.Empty;
                ssisName = package.Remove(package.IndexOf(".dtsx"));
                if (System.IO.File.Exists(string.Format("{0}\\{1}", baseFolderOnDisk, package)))
                {
                    // fileName = System.IO.FileInfo(string.Format("{0}\\{1}", baseFolderOnDisk, package)
                    if (!this.dtUtil.CopyPackage(server, string.Format("{0}\\{1}", baseFolderOnDisk, package), string.Format("{0}\\{1}", packagePath, ssisName)))
                    {
                        Console.Error.WriteLine(string.Format("Failed to upload {0}", package));
                        break;
                    }

                    results = true;
                }
                else
                {
                    results = false;
                    Console.WriteLine(string.Format("Unable to find package {0}\\{1}", baseFolderOnDisk, package));
                }
            }

            return results;
        }

        /// <summary>
        /// Create a list of packages to based on a SSIS manifest file.  The list will now be
        /// sorted so that packages created will be easier to find.
        /// </summary>
        /// <param name="manifestPath">A fully qualified path to a manifest document</param>
        /// <returns>A sorted list of package names</returns>
        private List<string> GetPackageListFromManifest(string manifestPath)
        {
            FileStream fileStream = null;
            XmlTextReader reader = null;
            System.Collections.Generic.List<string> packages = null;

            try
            {
                fileStream = File.Open(manifestPath, FileMode.Open);
                reader = new XmlTextReader(fileStream);
                packages = new System.Collections.Generic.List<string>();

                // <Package>HelloWorld.dtsx</Package></DTSDeploymentManifest>
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (string.Equals(reader.Name, "package", System.StringComparison.CurrentCultureIgnoreCase))
                        {
                            packages.Add(reader.ReadElementContentAsString());
                        }
                    }
                }

                packages.Sort();
            }
            catch (Exception exception)
            {
                // Console.WriteLine(exception.ToString());
            }

            return packages;
        }

        /// <summary>
        /// Create all the folders given a path
        /// </summary>
        /// <param name="server">A SQL Server servername</param>
        /// <param name="packagePath">A fully qualified SQL Server path</param>
        /// <returns>true if it was able to create all the folders</returns>
        private bool CreateFoldersRecursive(string server, string packagePath)
        {
            return this.dtUtil.CreateFoldersRecursive(server, packagePath);
        }
   }
}